// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging.Exceptions {

using System;
using System.IO;
using System.Text;

/// <summary>
/// An implementation of the IExceptionHandler interface which handles exceptions by writing their
/// information to an output stream
/// </summary>
public class StreamExceptionHandler : IExceptionHandler {
    private const int CLOSED                            = 0;
    private const int OPEN                              = 1;
    
    protected readonly static string NEW_LINE           = new string(new char[] {((char)13), ((char)10)});
    
    private Stream stream;
    private StreamWriter streamWriter;
    private Encoding encoding                           = Encoding.UTF8;
    
    private bool headerWritten                          = false;
    private int state                                   = CLOSED;
    private bool isLineBreakingEnabled                  = true;
    private bool isStreamClosingEnabled                 = false;
    
    private ExceptionFormatter exceptionFormatter       = new XMLExceptionFormatter();
    
    private object monitor                              = new object();
    
    /// <summary>
    /// Constructs a new instance.  Note that a stream MUST be set on the instance before the first
    /// call to the Open() method, or it will throw an exception
    /// </summary>
    public StreamExceptionHandler() {}
    
    /// <summary>
    /// Constructs a new instance that will use the specified stream and an instance of
    /// XMLExceptionFormatter for formatting exception information
    /// </summary>
    /// <param name="_stream">The stream to which the handler will write exception information</param>
    public StreamExceptionHandler(Stream _stream) {
        if (_stream == null) {
            throw new ArgumentNullException();
        }
        stream = _stream;
    }
    
    /// <summary>
    /// Constructs a new instance that will use the specified stream and ExceptionFormatter
    /// </summary>
    /// <param name="_stream">The stream to which the handler will write exception information</param>
    /// <param name="_exceptionFormatter">The ExceptionFormatter to use when formatting exception information</param>
    public StreamExceptionHandler(Stream _stream, ExceptionFormatter _exceptionFormatter) {
        if ((_stream == null) || (_exceptionFormatter == null)) {
            throw new ArgumentNullException();
        }
        stream = _stream;
        exceptionFormatter = _exceptionFormatter;
    }
    
    /// <summary>
    /// Gets and sets the underlying stream used by this instance to write out information
    /// on logging exceptions.  This property cannot be set while the ExceptionHandler is open
    /// </summary>
    public Stream Stream {
        get {
            return stream;
        }
        set {
            lock (monitor) {
                if (state == OPEN) {
                    throw new ArgumentNullException("This property cannot be set while the instance is open");
                }
                else {
                    stream = value;
                }
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the character encoding used by this instance.  This property cannot be set
    /// while the instance is open.  The default is System.Text.Encoding.UTF8
    /// </summary>
    public Encoding Encoding {
        get {
            return encoding;
        }
        set {
            if (value == null) {
                throw new ArgumentNullException();
            }
            lock (monitor) {
                if (state == OPEN) {
                    throw new ApplicationException("Cannot set this property while the instance is open");
                }
                encoding = value;
                exceptionFormatter.Encoding = encoding;
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the ExceptionFormatter used by this instance.  The default formatter type
    /// is XMLExceptionFormatter
    /// </summary>
    public ExceptionFormatter ExceptionFormatter {
        get {
            return exceptionFormatter;
        }
        set {
            if (value == null) {
                throw new ArgumentNullException();
            }
            exceptionFormatter = value;
        }
    }
    
    /// <summary>
    /// Gets and sets whether the instance inserts line breaks ((char)13 + (char)10) in the stream
    /// after every record.
    /// The default is true
    /// </summary>
    public bool IsLineBreakingEnabled {
        get {
            return isLineBreakingEnabled;
        }
        set {
            isLineBreakingEnabled = value;
        }
    }
    
    /// <summary>
    /// Gets and sets whether this instance closes the underlying stream when it is closed.
    /// The default is false
    /// </summary>
    public bool IsStreamClosingEnabled {
        get {
            return isStreamClosingEnabled;
        }
        set {
            isStreamClosingEnabled = value;
        }
    }
    
    /// <summary>
    /// Handles an exception thrown by a Logger instance during logging by writing information
    /// about the exception to the underlying stream
    /// </summary>
    /// <param name="exception">The logging exception to be handled</param>
    public void Handle(LoggerException exception) {
        if (exception == null) {
            return;
        }
        lock (monitor) {
            if (exception.IsBatchException) {
                try {
                    if (headerWritten == false) {
                        string listHeader = exceptionFormatter.GetExceptionListHeader();
                        if (listHeader != null) {
                            if (isLineBreakingEnabled) {
                                streamWriter.Write(NEW_LINE);
                            }
                            streamWriter.Write(listHeader);
                            if (isLineBreakingEnabled) {
                                streamWriter.Write(NEW_LINE);
                            }
                        }
                        headerWritten = true;
                    }
                    for(int x = 0; x < exception.Exceptions.Length; x++) {
                        streamWriter.Write(exceptionFormatter.Format(exception.Exceptions[x]));
                        if (isLineBreakingEnabled) {
                            streamWriter.Write(NEW_LINE);
                        }
                    }
                    streamWriter.Flush();
                } catch (Exception e) {
                    Console.WriteLine("Exception occurred while handling logging exception (stack trace follows):");
                    Console.WriteLine(e.StackTrace);
                }
            }
            else {
                try {
                    if (headerWritten == false) {
                        string listHeader = exceptionFormatter.GetExceptionListHeader();
                        if (listHeader != null) {
                            if (isLineBreakingEnabled) {
                                streamWriter.Write(NEW_LINE);
                            }
                            streamWriter.Write(listHeader);
                            if (isLineBreakingEnabled) {
                                streamWriter.Write(NEW_LINE);
                            }
                        }
                        headerWritten = true;
                    }
                    streamWriter.Write(exceptionFormatter.Format(exception));
                    if (isLineBreakingEnabled) {
                        streamWriter.Write(NEW_LINE);
                    }
                    streamWriter.Flush();
                } catch (Exception e) {
                    Console.WriteLine("Exception occurred while handling logging exception (stack trace follows):");
                    Console.WriteLine(e.StackTrace);
                }
            }
        }
    }
    
    /// <summary>
    /// Indicates whether the exception handler is open (i.e. can currently handle logging exceptions)
    /// </summary>
    public bool IsOpen {
        get {
            lock (monitor) {
                return (state == OPEN);
            }
        }
    }
    
    /// <summary>
    /// Enables the ExceptionHandler to begin handling logging exceptions,
    /// opening the underlying stream if necessary
    /// </summary>
    public void Open() {
        lock (monitor) {
            if (state == OPEN) {
                return;
            }
            if (stream == null) {
                throw new ApplicationException("No stream set-- cannot open");
            }
            streamWriter = new StreamWriter(stream, encoding, 65536);
            headerWritten = false;
            state = OPEN;
        }
    }
    
    /// <summary>
    /// Disables the ExceptionHandler for handling logging exceptions,
    /// closing the underlying stream if IsStreamClosingEnabled is true
    /// </summary>
    public void Close() {
        lock (monitor) {
            if (state == CLOSED) {
                return;
            }
            if (headerWritten) {
                string listFooter = exceptionFormatter.GetExceptionListFooter();
                if (listFooter != null) {
                    streamWriter.Write(listFooter);
                    if (isLineBreakingEnabled) {
                        streamWriter.Write(NEW_LINE);
                    }
                    try { streamWriter.Flush(); } catch {}
                }
            }
            if (isStreamClosingEnabled) {
                try { stream.Close(); } catch {}
                stream = null;
            }
            streamWriter = null;
            
            state = CLOSED;
        }
    }
    
}

}
